using System;
using System.Collections.Generic;
using System.Reflection;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Ext.Beans
{
    /**
     * @author Attila Szegedi
     * @version $Id: $
     */
    abstract class OverloadedMethod
    {
        internal static readonly object NoSuchMethod = new object();
        internal static readonly object AmbiguousMethod = new object();
        internal static readonly object[] EmptyArgs = new object[0];

        private Type[][] _marshalTypes;
        // TODO: make it not concurrent
        private readonly Dictionary<ClassString, object> _selectorCache = new Dictionary<ClassString, object>();
        private readonly LinkedList<MemberInfo> _members = new LinkedList<MemberInfo>();
        private readonly Dictionary<MemberInfo, Type[]> _signatures = new Dictionary<MemberInfo, Type[]>();
        
        internal void AddMember(MemberInfo member)
        {
            _members.AddLast(member);

            Type[] argTypes = MethodUtilities.GetParameterTypes(member);
            int l = argTypes.Length;
            _signatures.Add(member, (Type[]) argTypes.Clone());
            OnAddSignature(member, argTypes);
            if (_marshalTypes == null)
            {
                _marshalTypes = new Type[l + 1][];
                _marshalTypes[l] = argTypes;
                UpdateSignature(l);
            }
            else if (_marshalTypes.Length <= l)
            {
                var newMarshalTypes = new Type[l + 1][];
                Array.Copy(_marshalTypes, 0, newMarshalTypes, 0, _marshalTypes.Length);
                _marshalTypes = newMarshalTypes;
                _marshalTypes[l] = argTypes;
                UpdateSignature(l);
            }
            else
            {
                Type[] oldTypes = _marshalTypes[l]; 
                if (oldTypes == null)
                {
                    _marshalTypes[l] = argTypes;
                }
                else
                {
                    for (int i = 0; i < oldTypes.Length; ++i)
                    {
                        oldTypes[i] = MethodUtilities.GetMostSpecificCommonType(oldTypes[i], argTypes[i]);
                    }
                }
                UpdateSignature(l);
            }

            AfterSignatureAdded(l);
        }
        
        internal Type[] GetSignature(MemberInfo member)
        {
            return _signatures.GetValueOrDefault(member);
        }
        
        internal Type[][] GetMarshalTypes()
        {
    	    return _marshalTypes;
        }
        
        internal object GetMemberForArgs(object[] args, bool varArg)
        {
    	    var argTypes = new ClassString(args);
            object objMember;
            lock (_selectorCache)
            {
                objMember = _selectorCache.GetValueOrDefault(argTypes);
                if(objMember == null)
                {
                    objMember = argTypes.GetMostSpecific(_members, varArg);
                    _selectorCache.Add(argTypes, objMember);
                }
            }
    	    return objMember;
        }

        protected abstract void OnAddSignature(MemberInfo member, Type[] argTypes);
        protected abstract void UpdateSignature(int l);
        protected abstract void AfterSignatureAdded(int l);
        
        internal abstract object GetMemberAndArguments(IList<ITemplateModel> arguments, BeansWrapper w);
    }
}